#include <linux/fs.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pagemap.h>
#include <linux/kthread.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/netdevice.h>
#include <linux/ip.h>
#include <linux/in.h>
#include <linux/delay.h>
#include "ksocket.h"

#define ROOT_INO        1
#define MYFS_MAGIC      42
#define DEFAULT_PORT    62000
#define DEFAULT_HOST    "127.0.0.1"
#define MODULE_NAME     "FileTrigger"
#define BUFSIZE         1000
#define CMDBUFSIZE      64
#define TRUE            1
#define FALSE           0
#define UNUSED          0

#define STRING(var)     ((var)? var : "NULL")

// --- NOTES ---
// Name: d_instantiate --  fill in inode information for a dentry
// Synopsis: void d_instantiate (struct dentry * entry, struct inode * inode);
// arguments: entry = dentry to complete, inode = inode to attach to this dentry

// Parameters
static int directory_number = 0;
static int file_number = 0;
static int update_frequency = 0;
static char* filesystem_location = NULL;
static char* network_location = NULL;
static int encryption_method = 0;

// Filesystem globals
static ksocket_t ksock;
static unsigned long unique_id = 0;

// Forward declarations
static struct dentry* myfs_get_sb(struct file_system_type*, int, const char*, void*);
static ssize_t myfs_read(struct file*, char*, size_t, loff_t*);
static ssize_t myfs_write(struct file*, const char*, size_t, loff_t*);
static int myfs_permission(struct inode*, int);
static int myfs_getattr(struct vfsmount*, struct dentry*, struct kstat*);
static int myfs_create(struct inode*, struct dentry*, int, struct nameidata*);
static int myfs_mknod(struct inode*, struct dentry*, int, dev_t);
static int myfs_mkdir(struct inode*, struct dentry*, int);
static int myfs_rmdir(struct inode*, struct dentry*);
static struct inode* myfs_get_inode(struct super_block*, int, dev_t);
static void fetchTree(void);

static struct file_system_type myfs_type =
  {
    .owner          = THIS_MODULE,
    .name           = "myfs",
    .mount          = myfs_get_sb,
    .kill_sb        = kill_litter_super,
  };

static const struct super_operations myfs_s_ops =
  {
    .statfs             = simple_statfs,
    .drop_inode         = generic_delete_inode,
    .show_options       = generic_show_options,
  };

static struct file_operations myfs_file_ops =
  {
    .read               = myfs_read,
    .write              = myfs_write,
  };

static struct inode_operations myfs_file_inode_ops =
  {
    .permission         = myfs_permission,
    .getattr            = myfs_getattr,
    .create             = myfs_create,
    .mknod              = myfs_mknod,
  };

static const struct file_operations myfs_dir_ops =
  {
    .open               = dcache_dir_open,
    .release            = dcache_dir_close,
    .llseek             = dcache_dir_lseek,
    .read               = generic_read_dir,
    .readdir            = dcache_readdir,
    .fsync              = noop_fsync,
  };

static const struct inode_operations myfs_dir_inode_ops =
  {
    .mkdir              = myfs_mkdir,
    .rmdir              = myfs_rmdir,
    .create             = myfs_create,
    .mknod              = myfs_mknod,
    .lookup             = simple_lookup,
  };

static ssize_t myfs_read(struct file *file, char *userbuf,
                         size_t size, loff_t *off)
{
  const char *filename = "toto";
  char  buf[BUFSIZE];
  int   len;

  printk(KERN_INFO MODULE_NAME": myfs_read\n");

  if (*off >= (file->f_dentry->d_inode->i_size)) return 0;

  len = snprintf(buf, BUFSIZE, "read %s %d %lld\n", filename, size, *off);
  if (ksend(ksock, buf, len, 0) == -1) return -1;

  if (*off >= size) return 0;
  len = krecv(ksock, buf, BUFSIZE, 0);
  if (len <= 0) return 0;
  if (*off >= len) return 0;
  if (size > len - *off)
    size = len - *off;
  if (copy_to_user(userbuf, buf, len)) return -EFAULT;
  *off += len;
  return len;
}

static ssize_t myfs_write(struct file *file, const char *userbuf,
                          size_t size, loff_t *off)
{
  int len;
  char  buf[500], cmd[500];
  const char *filename = "toto";

  printk(KERN_INFO MODULE_NAME": myfs_write\n");
  len = snprintf(cmd, CMDBUFSIZE, "write %s %d %lld\n",
                 filename, size, (file->f_dentry->d_inode->i_size));
  printk(KERN_INFO MODULE_NAME": cmd = %s\n", cmd);

  len = ksend(ksock, cmd, len, 0);
  if (len <= 0) return len;

  if (copy_from_user(buf, userbuf, size))
    {
      printk("in myfs_write; copy from user failed");
      ksend(ksock, buf, 0, 0);
      return -EFAULT;
    }

  if (ksend(ksock, buf, size, 0) < 0)
    return -1;

  len = krecv(ksock, buf, BUFSIZE - 1, 0);
  if (len <= 0) return len;
  file->f_dentry->d_inode->i_size += size;
  buf[size + 1] = 0;

  return size;
}

static int myfs_mkdir(struct inode *dir, struct dentry *dentry, int mode)
{
  int retval = myfs_mknod(dir, dentry, mode | S_IFDIR, 0);

  if (!retval)
    inc_nlink(dir);
  return retval;
}

static int myfs_rmdir(struct inode* dir, struct dentry* dentry)
{
  return simple_rmdir(dir, dentry);
}

static int myfs_permission(struct inode* inode, int mask)
{
  return generic_permission(inode, mask);
}

static int myfs_getattr(struct vfsmount *mnt, struct dentry *d, struct kstat *s)
{
  struct inode* inode = d->d_inode;

  if (inode)
    generic_fillattr(inode, s);
  return 0;
}

static int myfs_create(struct inode *dir, struct dentry *dentry,
                       int mode, struct nameidata *id)
{
  int  rv;
  char buf[CMDBUFSIZE];
  struct inode* inode = myfs_get_inode(dir->i_sb, mode, 0);

  printk(KERN_INFO MODULE_NAME": myfs_create\n");
  printk(KERN_INFO MODULE_NAME": rights = %o\n", mode);

  rv = snprintf(buf, CMDBUFSIZE, "create %lu %d", unique_id, mode);
  rv = ksend(ksock, buf, rv, 0);

  if (rv == 0 || rv == -1)
    return rv;

  if (krecv(ksock, buf, CMDBUFSIZE, 0) < 0)
    return -1;

  if (inode)
    {
      inode->i_private = (void*)unique_id++;
      inode->i_size = 0;
      d_instantiate(dentry, inode);
      dget(dentry);
    }
  else
    {
      printk("allocate inode failed\n");
    }
  return 0;
}

static int  myfs_mknod(struct inode *dir, struct dentry *dentry,
                       int mode, dev_t dev)
{
  struct inode * inode = myfs_get_inode(dir->i_sb, mode, dev);
  int error = -ENOSPC;

  if (inode)
    {
      if (dir->i_mode & S_ISGID)
        {
          inode->i_gid = dir->i_gid;
          if (S_ISDIR(mode))
            inode->i_mode |= S_ISGID;
        }
      d_instantiate(dentry, inode);
      dget(dentry);
      error = 0;
      dir->i_mtime = dir->i_ctime = CURRENT_TIME;
    }
  return error;
}

static struct inode* myfs_get_inode(struct super_block *sb, int mode, dev_t dev)
{
  struct inode* inode = new_inode(sb);

  if (inode)
    {
      // DEBUG
      //inode->i_mode = 0644;
      inode->i_mode = mode;
      inode->i_uid = current_fsuid();
      inode->i_gid = current_fsgid();
      inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;
      switch (mode & S_IFMT)
        {
        case S_IFREG:
          inode->i_op = &myfs_file_inode_ops;
          inode->i_fop = &myfs_file_ops;
          break;
        case S_IFDIR:
          inode->i_op = &myfs_dir_inode_ops;
          inode->i_fop = &myfs_dir_ops;
          /* directory inodes start off with i_nlink == 2 (for "." entry) */
          inc_nlink(inode);
          break;
        case S_IFLNK:
          inode->i_op = &page_symlink_inode_operations;
          break;
        default:
          init_special_inode(inode, mode, dev);
          break;
        }
    }
  return inode;
}

static struct dentry*   create_file(struct super_block* sb,
                                    struct dentry* dir,
                                    const char* name)
{
  struct dentry* dentry;
  struct inode* inode;
  struct qstr   qname;

  if (name) printk(KERN_INFO MODULE_NAME": createfile %s\n", name);

  qname.name = name;
  qname.len = strlen(name);
  qname.hash = full_name_hash(name, qname.len);

  dentry = d_alloc(dir, &qname);
  if (!dentry) goto out;

  inode = myfs_get_inode(sb, S_IFREG | 0644, 0);
  if (!inode) goto out_dput;

  d_add(dentry, inode);
  return dentry;

 out_dput:
  dput(dentry);

 out:
  return 0;
}

static int myfs_fill_super(struct super_block *sb, void *data, int silent)
{
  struct inode* root;
  struct dentry *root_dentry;

  sb->s_blocksize = PAGE_CACHE_SIZE;
  sb->s_blocksize_bits = PAGE_CACHE_SHIFT;
  sb->s_magic = MYFS_MAGIC;
  sb->s_op = &myfs_s_ops;

  root = myfs_get_inode(sb, S_IFDIR | 0755, 0);
  if (root == NULL)
    goto out;

  root->i_op = &myfs_dir_inode_ops;
  root->i_fop = &myfs_dir_ops;

  root_dentry = d_alloc_root(root);
  if (! root_dentry)
    goto out_iput;
  sb->s_root = root_dentry;

  create_file(sb, root_dentry, "lost+found");

  return 0;

 out_iput:
  iput(root);
 out:
  return -ENOMEM;
}

static struct dentry*	myfs_get_sb(struct file_system_type *fs_type, int flags,
				    const char *dev_name, void *data)
{
  return mount_nodev(fs_type, flags, data, myfs_fill_super);
  //  return get_sb_single(fs_type, flags, data, myfs_fill_super, mnt);
}

// Retrieve directories tree from server
static void fetchTree(void)
{
}

int __init myfs_init(void)
{
  int   err;
  int   addr_len;
  struct sockaddr_in sockaddr;

  err = register_filesystem(&myfs_type);
  if (err) goto out;

  ksock = ksocket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  if (ksock == NULL) goto out;

  memset(&sockaddr, 0, sizeof(sockaddr));
  sockaddr.sin_family = AF_INET;
  sockaddr.sin_port = htons(DEFAULT_PORT);
  sockaddr.sin_addr.s_addr = inet_addr(DEFAULT_HOST);
  addr_len = sizeof(struct sockaddr_in);

  printk(KERN_INFO MODULE_NAME": Trying to connect\n");
  err = kconnect(ksock, (struct sockaddr*) &sockaddr, addr_len);
  if (err < 0) goto out_release;

  fetchTree();

  // Sucessful loading
  printk(KERN_INFO MODULE_NAME": --- ~~~~~~~~~~~~~ ---\n");
  printk(KERN_INFO MODULE_NAME": --- Module loaded ---\n");
  printk(KERN_INFO MODULE_NAME": --- ~~~~~~~~~~~~~ ---\n");
  return err;

 out_release:
  kclose(ksock);
  printk(KERN_INFO MODULE_NAME": Failed and leaving\n");
 out:
  return err;
}

void __exit myfs_exit(void)
{
  printk(KERN_INFO MODULE_NAME": --- ~~~~~~~~~~~~~~~ ---\n");
  printk(KERN_INFO MODULE_NAME": --- Module unloaded ---\n");
  printk(KERN_INFO MODULE_NAME": --- ~~~~~~~~~~~~~~~ ---\n");

  if (ksock) kclose(ksock);
  unregister_filesystem(&myfs_type);
}

static void     display_parameters(void)
{
  printk(KERN_INFO MODULE_NAME": directory_number = %d\n", directory_number);
  printk(KERN_INFO MODULE_NAME": file_number = %d\n", file_number);
  printk(KERN_INFO MODULE_NAME": update_frequency = %d\n", update_frequency);
  printk(KERN_INFO MODULE_NAME": filesystem_location = %s\n", STRING(filesystem_location));
  printk(KERN_INFO MODULE_NAME": network_location = %s\n", STRING(network_location));
  printk(KERN_INFO MODULE_NAME": encryption_method = %d\n", encryption_method);
}

/* init and cleanup functions */
module_init(myfs_init);
module_exit(myfs_exit);

/* Parameters */
module_param(directory_number, int, 0);
module_param(file_number, int, 0);
module_param(update_frequency, int, 0);
module_param(filesystem_location, charp, 0);
module_param(network_location, charp, 0);
module_param(encryption_method, int, 0);

/* Descriptions */
MODULE_PARM_DESC(directory_number, "Directory number.");
MODULE_PARM_DESC(file_number, "File number.");
MODULE_PARM_DESC(update_frequency, "Update frequency.");
MODULE_PARM_DESC(filesystem_location, "File system location.");
MODULE_PARM_DESC(network_location, "Network location.");
MODULE_PARM_DESC(encryption_method, "Encryption method.");

/* module information */
MODULE_DESCRIPTION("FileSystem client");
MODULE_LICENSE("GPL");
